#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <sys/select.h>
#include "Sock.hpp"
#include "log.hpp"

const static int gport = 8000;
typedef int type_t;

class SelectServer
{
    static const int N = (sizeof(fd_set) * 8); // 位图

public:
    SelectServer(uint16_t port = gport)
        : _port(port)
    {
    }

    void InitServer()
    {
        _listensock.Socket();
        _listensock.Bind(_port);
        _listensock.Listen();

        for (int i = 0; i < N; i++)
        {
            _fdarray[i] = defaultfd; // 初始化位图
        }
    }
    void Start()
    {
        // 这里不能直接获取新的连接。因为服务器最开始并没有太多连接。因此新连接到来可以当作是读事件就绪
        _fdarray[0] = _listensock.Fd();
        while (true)
        {
            fd_set rfds; // 读的套接字集合,是一个输入输出型参数，每次都需要重置
            FD_ZERO(&rfds);
            int maxfd = _fdarray[0]; // 初始化为监听套接字
            for (int i = 0; i < N; i++)
            {
                if (_fdarray[i] == defaultfd)
                    continue;

                //合法fd
                FD_SET(_fdarray[i], &rfds); // 将文件描述符fd加入读集合
                if (maxfd < _fdarray[i])
                    maxfd = _fdarray[i]; // 更新最大文件描述符
            }

            int n = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr); // select监视的文件描述符范围为[0,maxfd]
            switch (n)
            {
            case 0: // 超时
                logMessage(Debug, "timeout, %d: %s", errno, strerror(errno));
                break;
            case -1: // 失败
                logMessage(Warning, "%d: %s", errno, strerror(errno));
                break;
            default: // 成功
                logMessage(Debug, "有一个就绪事件发生了: %d", n);
                HandlerEvent(rfds); // 处理事件
                break;
            }
        }
    }

    void HandlerEvent(fd_set &rfds)
    {
        for (int i = 0; i < N; i++)
        {
            if (_fdarray[i] == defaultfd)
                continue;
            if ((_fdarray[i] == _listensock.Fd()) && FD_ISSET(_listensock.Fd(), &rfds))
            {   //当前为监听套接字，且监听套接字在集合中，表示有新的连接请求到达
                Accepter();
            }
            else if ((_fdarray[i] != _listensock.Fd()) && FD_ISSET(_fdarray[i], &rfds))
            {
                //是合法套接字但不是监听套接字，说明有数据可以读取
                int fd=_fdarray[i];
                char buffer[1024];//读取缓冲区
                ssize_t s=recv(fd,buffer,sizeof(buffer)-1,0);//本次读取不会被阻塞，但不保证数据被完整读取
                //因为buffer是1024，而底层数据可能是2048.因此这里存在一些问题
                if(s>0)
                {
                    buffer[s-1] = 0;
                    std::cout << "client# " << buffer << std::endl;

                    // 发送回去也要被select管理的,TODO
                    std::string echo = buffer;
                    echo += " [select server echo]";
                    send(fd, echo.c_str(), echo.size(), 0);
                }
                else
                {
                    if (s == 0)//客户端主动关闭连接
                        logMessage(Info, "client quit ..., fdarray_[i] -> defaultfd: %d->%d", fd, defaultfd);
                    else//数据接收错误
                        logMessage(Warning, "recv error, client quit ..., fdarray_[i] -> defaultfd: %d->%d", fd, defaultfd);
                    close(_fdarray[i]);
                    _fdarray[i] = defaultfd;
                }
            }
        }
    }

    void Accepter()//在进行的Accepter不会被阻塞
    {
        std::string clientip;
        uint16_t clientport;
        int sock=_listensock.Accept(&clientip,&clientport);//得到了对应的sock，不代表可以进行read/recv读取sock，因为sock上不一定有数据传输
        //因此需要将sock交付给select，有select管理
        if(sock<0)  return;
        logMessage(Debug, "[%s:%d], sock: %d", clientip.c_str(), clientport, sock);
        int pos = 1;
        for (; pos < N; pos++)
        {
            if (_fdarray[pos] == defaultfd)
                break;
        }
        if (pos >= N)//数组满了，无法容纳新的套接字，即不能建立新连接
        {
            close(sock);
            logMessage(Warning, "sockfd array[] full");
        }
        else
        {
            _fdarray[pos] = sock;
        }
    }

    ~SelectServer()
    {
        _listensock.Close();
    }

private:
    uint16_t _port;
    Sock _listensock;
    type_t _fdarray[N]; // 程序管理所有的fd，通过该数组与内核中的fd_set交换信息
};